#!/usr/bin/php -q
<?php
error_reporting(E_ALL);
/* Note: this is non-forking server, so only one client at the time */

set_time_limit(0);
ob_implicit_flush();
declare(ticks = 1);

if (!isset($argv[1]) || !in_array($argv[1],array("-d","-f")))
{
  echo "Usage: ".$argv[0]." -d|-f\n\n   -d   run as a daemon\n   -f   run in foreground\n";
  die;
}

$daemon = false;

if ($argv[1] == "-d")
{
  $daemon = true;
  become_daemon();
}

// setup signal handlers
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP,  "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");

// Server IP address
$address = "127.0.0.1";
 
// Port to listen
$port = 5000;
 
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($socket,$address, $port) or die('Could not bind to address'); 
socket_listen($socket, 5);

while (true)
{
  $client = socket_accept($socket);
  
  if (!$daemon)
  {
    echo "### Client connected\n";
  }
  
  $input = "";
  
  while (1)
  {
    $recv = "";
    $recv = trim(socket_read($client, '1400'));
    
    if ($recv != "")
    {
      $input.= $recv;
    }
    
    if (!$recv || substr($recv,-15) == "</RequestBlock>")
    {
      break;
    }
  }
  
  if (!$daemon)
  {
    echo "RX <--\n$input\n";
  }
  
  if (!$xml = @simplexml_load_string($input))
  {
    write($client,getResponse('invalid'));
    socket_close($client);
    continue;
  }
  
  $request = object2array($xml);
  
  if (!$request || !is_array($request) || !isset($request['Request']) || !isset($request['Request']['@attributes'])
    || !isset($request['Request']['@attributes']['Type']))
  {
    write($client,getResponse('invalid'));
    socket_close($client);
    continue;
  }
  
  switch ($request['Request']['@attributes']['Type'])
  {
    case 'AUTH':
    case 'AUTHREVERSAL':
      write($client,getResponse('AUTH',$request));
      break;
    case 'SETTLEMENT':
      write($client,getResponse('SETTLEMENT'));
      break;
    default:
      write($client,getResponse('invalid'));
  }

  
  socket_close($client);
  
  if (!$daemon)
  {
    echo "### Client disconnected ###\n";
  }
  
  continue;
}

socket_close($socket);

function sig_handler($signo)
{
  socket_close($socket);
}

function write($socket,$data)
{
  global $daemon;
  
  if (!$daemon)
  {
    echo "TX -->\n$data\n";
  }
  
  socket_write($socket, $data, strlen ($data));
  socket_write($socket, "\r\n", strlen ("\r\n"));
}

/**
 * Gets XML response
 * 
 * @param string $type
 * @return string
 */
function getResponse($type,$request = null)
{
  $timestamp = date("Y-m-d H:i:s");
  $request_ref = random_str(10);
  $transaction_ref = random_num(3)."-".random_num(1)."-".random_num(5);
  $transaction_verifier = base64_encode(random_str(128));
  $date = date("Y-m-d");
  
  switch ($type)
  {
    case 'SETTLEMENT':
$xml=<<<EOD
<?xml version="1.0" encoding="UTF-8"?>
<ResponseBlock Live="" Version="3.51">
  <Response Type="SETTLEMENT">
    <OperationResponse>
      <SettleDate>$date</SettleDate>
      <SettleAmount>497</SettleAmount>
      <Currency>GBP</Currency>
      <SettledAmount>0</SettledAmount>
      <TransactionReference>$transaction_ref</TransactionReference>
      <Result>1</Result>
      <SettleStatus>1</SettleStatus>
    </OperationResponse>
  </Response>
</ResponseBlock>
EOD;
      break;
    case 'AUTH':
      
      $number = null;
      
      if (is_array($request) && isset($request['Request']) && isset($request['Request']['PaymentMethod'])
        && isset($request['Request']['PaymentMethod']['CreditCard']) && isset($request['Request']['PaymentMethod']['CreditCard']['Number']))
      {
        $number = $request['Request']['PaymentMethod']['CreditCard']['Number'];
      }
      
      if (substr($number,0,1) == '6')
      {
$xml=<<<EOD
<?xml version="1.0" encoding="UTF-8"?>
<ResponseBlock Live="" Version="3.51">
  <Response Type="AUTH">
    <OperationResponse>
      <TransactionReference>$transaction_ref</TransactionReference>
      <TransactionCompletedTimestamp>$timestamp</TransactionCompletedTimestamp>
      <AuthCode>51/Not sufficient funds</AuthCode>
      <Result>2</Result>
      <SettleStatus>2</SettleStatus>
    </OperationResponse>
    <Order>
      <OrderInformation>This is a test order</OrderInformation>
      <OrderReference>Order0001</OrderReference>
    </Order>
  </Response>
</ResponseBlock>

EOD;
      }
      else
      {
$xml=<<<EOD
<?xml version="1.0" encoding="UTF-8"?>
<ResponseBlock Live="" Version="3.51">
  <Response Type="AUTH">
    <OperationResponse>
      <TransactionReference>$transaction_ref</TransactionReference>
      <TransactionCompletedTimestamp>$timestamp</TransactionCompletedTimestamp>
      <AuthCode>AUTH CODE:NATW</AuthCode>
      <TransactionVerifier>$transaction_verifier</TransactionVerifier>
      <Result>1</Result>
      <SettleStatus>0</SettleStatus>
      <SecurityResponseSecurityCode>2</SecurityResponseSecurityCode>
      <SecurityResponsePostCode>1</SecurityResponsePostCode>
      <SecurityResponseAddress>1</SecurityResponseAddress>
    </OperationResponse>
    <Order>
      <OrderInformation>This is a test order</OrderInformation>
      <OrderReference>Order0001</OrderReference>
    </Order>
  </Response>
</ResponseBlock>

EOD;
    }
      break;
    default:
$xml=<<<EOD
<?xml version="1.0" encoding="utf-8"?>
<ResponseBlock>
<Response Type="ERROR">
<RequestReference>$request_ref</RequestReference>
<OperationResponse>
<Result>0</Result>
<Message>(10500) Invalid or incomplete XML Request</Message>
<TransactionCompletedTimestamp>$timestamp</TransactionCompletedTimestamp>
</OperationResponse>
<Order/>
<Optional/>
</Response>
</ResponseBlock>
EOD;
  }
  
  return $xml;
}


/**
 * Become a daemon by forking and closing the parent
 */
function become_daemon()
{
  $pid = pcntl_fork();
 
  if ($pid == -1)
  {
    /* fork failed */
    echo "fork failure!\n";
    exit();
  }
  elseif ($pid)
  {
    /* close the parent */
    exit();
  }
  else
  {
    /* child becomes our daemon */
    posix_setsid();
    chdir('/');
    umask(0);
    return posix_getpid();
  }
}

  
/**
 * Converts object to an array
 * 
 * @param mixed $boject
 * 
 * @return array or null
 */
function object2array($object)
{
  $return = NULL;
  
  if (is_array($object))
  {
    foreach($object as $key => $value)
    {
      $return[$key] = object2array($value);
    }
  }
  else
  {
    $var = get_object_vars($object);
      
    if ($var)
    {
      foreach($var as $key => $value)
      {
        $return[$key] = ($key && !$value) ? NULL : object2array($value);
      }
    }
    else
    {
      return $object;
    }
  }
  
  return $return;
}

  
/**
 * Generates random string.
 *
 * @param    Length Length of the password (optional).
 * @return   String with generated password.
 */
function random_str($length = 6)
{
  $allowable_characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
  
  $len = strlen($allowable_characters) - 1;
  
  $pass = '';
  
  for ($i = 0; $i < $length; $i++)
  {
    $pass .= $allowable_characters[mt_rand(0, $len)];
  }
  
  return $pass;
}

/**
 * Generates random number.
 *
 * @param    Length Length of the password (optional).
 * @return   String with generated password.
 */
function random_num($length = 6)
{
  $allowable_characters = '0123456789';
  
  $len = strlen($allowable_characters) - 1;
  
  $pass = '';
  
  for ($i = 0; $i < $length; $i++)
  {
  
    if ($i == 0)
    {
      $pass .= $allowable_characters[mt_rand(1, $len)];
    }
    else
    {
      $pass .= $allowable_characters[mt_rand(0, $len)];
    }
  }
  
  return $pass;
}

?>
